
/*-------------------------------------------------------------------------
 *
 * ipc.c
 *	  POSTGRES 进程间通信定义。
 *
 * 这个文件命名不当，因为它现在与 IPC 的直接关系不大。这里的功能与管理
 * 后台或主进程的退出清理有关。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/ipc.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>

#include "miscadmin.h"
#ifdef PROFILE_PID_DIR
#include "postmaster/autovacuum.h"
#endif
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "tcop/tcopprot.h"


/*
 * 此标志在proc_exit（）期间设置，以更改ereport（）的行为，
 * 以便来自on_proc_exit例程的ereport（）无法让我们退出
 * 退出过程。我们不想回到空闲循环...
 */
bool		proc_exit_inprogress = false;

/*
 * 当shmem_exit（）正在进行时设置的标志。
 */
bool		shmem_exit_inprogress = false;

/*
 * 此标志跟踪我们是否在当前进程（或在父postmaster中）调用了atexit（）。
 */
static bool atexit_callback_setup = false;

/* 本地函数 */
static void fc_proc_exit_prepare(int fc_code);


/* ----------------------------------------------------------------
 *						exit() 处理
 *
 * 这些函数一般与atexit()相同精神，
 * 但提供了一些我们需要的额外功能 --- 特别是，
 * 我们想要注册回调函数，以便在我们断开
 * 一个破损的共享内存上下文但未退出postmaster时调用。
 *
 * 回调函数可以接受零个、一个或两个参数：第一个传递
 * 的参数是整数exitcode，第二个是在
 * 注册回调时提供的Datum。
 * ----------------------------------------------------------------
 */

#define MAX_ON_EXITS 20

struct ONEXIT
{
	pg_on_exit_callback function;
	Datum		arg;
};

static struct ONEXIT on_proc_exit_list[MAX_ON_EXITS];
static struct ONEXIT on_shmem_exit_list[MAX_ON_EXITS];
static struct ONEXIT before_shmem_exit_list[MAX_ON_EXITS];

static int	on_proc_exit_index,
			on_shmem_exit_index,
			before_shmem_exit_index;


/* ----------------------------------------------------------------
 *		proc_exit
 *
 *		此函数调用为其注册的所有回调
 *		（以释放资源），然后调用exit。
 *
 *		这应该是唯一调用exit()的函数。
 *		-cim 2/6/90
 *
 *		不幸的是，我们不能保证附加代码
 *		遵守不直接调用exit()的规则。因此，虽然
 *		这是退出系统的首选方法，我们还注册
 *		一个atexit回调，以确保清理发生。
 * ----------------------------------------------------------------
 */
void proc_exit(int fc_code)
{
	/* 如果通过system()等方法派生，则不安全。 */
	if (MyProcPid != (int) getpid())
		elog(PANIC, "proc_exit() called in child process");

	/* 清理所有必须清理的内容 */
	fc_proc_exit_prepare(fc_code);

#ifdef PROFILE_PID_DIR
	{
		
/*
		 * 如果我们正在对自己进行分析，那么 gprof 的 mcleanup() 将要
		 * 写出一个配置文件到 ./gmon.out。由于 mcleanup() 始终使用固定的
		 * 文件名，每个后台将覆盖之前的配置文件。为了解决这个问题，
		 * 我们为每个后台创建一个单独的子目录（./gprof/pid），并在我们
		 * exit() 之前 'cd' 到该子目录 - 这强制 mcleanup() 将每个配置文件
		 * 写入其自己的目录。我们最终得到的结果类似于：$PGDATA/gprof/8829/gmon.out
		 * $PGDATA/gprof/8845/gmon.out ...
		 *
		 * 为了避免不必要的磁盘空间膨胀，自动清理工作进程是被区别对待的：
		 * 它们的 gmon.out 文件都放入同一个子目录中。没有这个，
		 * 一个“只是静止在那里”的安装每几秒钟仍然会占用数MB的磁盘空间。
		 *
		 * 请注意，我们在这里执行此操作，而不是在 on_proc_exit() 回调中，
		 * 因为我们希望确保这段代码最后执行 - 我们不想干扰任何其他的
		 * on_proc_exit() 回调。出于同样的原因，我们没有将它包含在
		 * proc_exit_prepare 中……所以如果你以“错误的方式”退出，你将
		 * 不会将配置文件保存在一个好的地方。
		 */
		char		fc_gprofDirName[32];

		if (IsAutoVacuumWorkerProcess())
			snprintf(fc_gprofDirName, 32, "gprof/avworker");
		else
			snprintf(fc_gprofDirName, 32, "gprof/%d", (int) getpid());

		/*
		 * 使用 mkdir() 而不是 MakePGDirectory()，因为我们在这里
		 * 并没有创建一个 PG 目录。
		 */
		mkdir("gprof", S_IRWXU | S_IRWXG | S_IRWXO);
		mkdir(fc_gprofDirName, S_IRWXU | S_IRWXG | S_IRWXO);
		chdir(fc_gprofDirName);
	}
#endif

	elog(DEBUG3, "exit(%d)", fc_code);

	exit(fc_code);
}

/*
 * 在 proc_exit 和 atexit 处理程序之间共享的代码。请注意，
 * 通过 proc_exit 的正常退出，这将实际上被调用两次……
 * 但第二次调用与此无关。
 */
static void fc_proc_exit_prepare(int fc_code)
{
	/*
	 * 一旦我们设置了这个标志，我们就已经决定退出。任何 ereport()
	 * 将不会将控制权返回给主循环，而是直接返回到这里。
	 */
	proc_exit_inprogress = true;

	/*
	 * 忘记任何待处理的取消或退出请求；我们已经在尽力
	 * 关店了。请注意，信号处理程序不会再次设置这些标志，
	 * 现在 proc_exit_inprogress 已经设置。
	 */
	InterruptPending = false;
	ProcDiePending = false;
	QueryCancelPending = false;
	InterruptHoldoffCount = 1;
	CritSectionCount = 0;

	/*
	 * 同时清除错误上下文栈，以防在 proc_exit 期间由于任何 elog/ereport
	 * 调用而被调用的错误回调。无论它们可能想提供什么上下文都可能
	 * 不相关，并且在我们进行了诸如中止任何打开的事务等操作之后，
	 * 它们可能会完全失败。（在正常退出场景中，上下文栈应该是空的，
	 * 但在例如 elog(FATAL) 的情况下可能不是。）
	 */
	error_context_stack = NULL;
	/* 出于相同的原因，在被覆盖之前重置 debug_query_string */
	debug_query_string = NULL;

	/* 首先执行我们的共享内存退出 */
	shmem_exit(fc_code);

	elog(DEBUG3, "proc_exit(%d): %d callbacks to make",
		 fc_code, on_proc_exit_index);

	/*
	 * 调用所有已注册的回调。
	 *
	 * 请注意，由于我们每次都会减少 on_proc_exit_index，
	 * 如果某个回调调用 ereport(ERROR) 或 ereport(FATAL)，
	 * 则在控制权返回到这里时不会再次调用它（之前完成的回调
	 * 也不会）。因此，不应该出现无限循环的情况。
	 */
	while (--on_proc_exit_index >= 0)
		on_proc_exit_list[on_proc_exit_index].function(fc_code,
													   on_proc_exit_list[on_proc_exit_index].arg);

	on_proc_exit_index = 0;
}

/* ------------------
 * 运行所有的 on_shmem_exit 例程 --- 但实际上并不退出。
 * 这被主进程用于在后台进程严重失败后重新初始化共享内存和
 * 信号量。与 proc_exit() 一样，我们在调用每个回调之前
 * 从列表中移除它，以避免在发生错误时出现无限循环。
 * ------------------
 */
void shmem_exit(int fc_code)
{
	shmem_exit_inprogress = true;

	/*
	 * 调用 before_shmem_exit 回调。
	 *
	 * 这些应该是需要大多数系统仍然正常工作的事情，
	 * 例如需要目录访问的临时关系的清理；或者需要完成的事情，
	 * 因为后续的清理步骤依赖于它们，例如释放 lwlocks。
	 */
	elog(DEBUG3, "shmem_exit(%d): %d before_shmem_exit callbacks to make",
		 fc_code, before_shmem_exit_index);
	while (--before_shmem_exit_index >= 0)
		before_shmem_exit_list[before_shmem_exit_index].function(fc_code,
																 before_shmem_exit_list[before_shmem_exit_index].arg);
	before_shmem_exit_index = 0;

	/*
	 * 调用动态共享内存回调。
	 *
	 * 这些与后期回调具有相同的目的，但针对动态共享内存段
	 * 而不是主共享内存段。 dsm_backend_shutdown() 具有我们
	 * 用于主共享内存段的相同渐进逻辑；即，在调用之前注销每个
	 * 回调，以便如果其中一个回调本身抛出 ERROR 或 FATAL，
	 * 我们不会陷入无限循环。
	 *
	 * 请注意，在这里显式调用此函数与将其注册为
	 * on_shmem_exit 回调有着很大的不同，正是因为这个原因：
	 * 如果一个动态共享内存回调出错，剩余的回调仍将被调用。
	 * 因此，硬编码此调用使其与主共享内存段的回调处于
	 * 同等地位。
	 */
	dsm_backend_shutdown();

	/*
	 * 调用 on_shmem_exit 回调函数。
	 *
	 * 这些通常是释放低级共享内存资源。在某些情况下，这是防止早期
	 * 回调可能失败的后备，可能导致重新进入此例程；在其他情况下，
	 * 这是仅在进程退出时发生的清理工作。
	 */
	elog(DEBUG3, "shmem_exit(%d): %d on_shmem_exit callbacks to make",
		 fc_code, on_shmem_exit_index);
	while (--on_shmem_exit_index >= 0)
		on_shmem_exit_list[on_shmem_exit_index].function(fc_code,
														 on_shmem_exit_list[on_shmem_exit_index].arg);
	on_shmem_exit_index = 0;

	shmem_exit_inprogress = false;
}

/* ----------------------------------------------------------------
 *		atexit_callback
 *
 *		后备，以确保直接调用 exit() 不会给我们带来麻烦。
 *
 * 一个非常不配合的人可能会调用 _exit()，
 * 但在这种情况下，我们有一个“灭亡开关”会让
 * postmaster 将其视为崩溃 --- 参见 pmsignal.c。
 * ----------------------------------------------------------------
 */
static void fc_atexit_callback(void)
{
	/* 清理所有必须清理的内容 */
	/* ... 可惜我们不知道真实的退出代码 ... */
	fc_proc_exit_prepare(-1);
}

/* ----------------------------------------------------------------
 *		on_proc_exit
 *
 *		此函数将回调函数添加到 proc_exit() 调用的函数列表中。   -cim 1990年2月6日
 * ----------------------------------------------------------------
 */
void on_proc_exit(pg_on_exit_callback fc_function, Datum fc_arg)
{
	if (on_proc_exit_index >= MAX_ON_EXITS)
		ereport(FATAL,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg_internal("out of on_proc_exit slots")));

	on_proc_exit_list[on_proc_exit_index].function = fc_function;
	on_proc_exit_list[on_proc_exit_index].arg = fc_arg;

	++on_proc_exit_index;

	if (!atexit_callback_setup)
	{
		atexit(fc_atexit_callback);
		atexit_callback_setup = true;
	}
}

/* ----------------------------------------------------------------
 *		before_shmem_exit
 *
 *		注册早期回调以进行用户级清理，
 *		例如事务中止，在我们开始关闭
 *		低级子系统之前。
 * ----------------------------------------------------------------
 */
void before_shmem_exit(pg_on_exit_callback fc_function, Datum fc_arg)
{
	if (before_shmem_exit_index >= MAX_ON_EXITS)
		ereport(FATAL,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg_internal("out of before_shmem_exit slots")));

	before_shmem_exit_list[before_shmem_exit_index].function = fc_function;
	before_shmem_exit_list[before_shmem_exit_index].arg = fc_arg;

	++before_shmem_exit_index;

	if (!atexit_callback_setup)
	{
		atexit(fc_atexit_callback);
		atexit_callback_setup = true;
	}
}

/* ----------------------------------------------------------------
 *		on_shmem_exit
 *
 *		注册普通回调以执行低级关闭
 *		（例如释放我们的 PGPROC）；在 before_shmem_exit
 *		回调之后和 on_proc_exit 回调之前运行。
 * ----------------------------------------------------------------
 */
void on_shmem_exit(pg_on_exit_callback fc_function, Datum fc_arg)
{
	if (on_shmem_exit_index >= MAX_ON_EXITS)
		ereport(FATAL,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg_internal("out of on_shmem_exit slots")));

	on_shmem_exit_list[on_shmem_exit_index].function = fc_function;
	on_shmem_exit_list[on_shmem_exit_index].arg = fc_arg;

	++on_shmem_exit_index;

	if (!atexit_callback_setup)
	{
		atexit(fc_atexit_callback);
		atexit_callback_setup = true;
	}
}

/* ----------------------------------------------------------------
 *		cancel_before_shmem_exit
 *
 *		此函数移除之前注册的 before_shmem_exit
 *		回调。我们仅查看最新的条目进行移除，因为我们
 * 		期望调用者严格按照 LIFO 顺序添加和移除临时的 before_shmem_exit
 * 		回调。
 * ----------------------------------------------------------------
 */
void cancel_before_shmem_exit(pg_on_exit_callback fc_function, Datum fc_arg)
{
	if (before_shmem_exit_index > 0 &&
		before_shmem_exit_list[before_shmem_exit_index - 1].function
		== fc_function &&
		before_shmem_exit_list[before_shmem_exit_index - 1].arg == fc_arg)
		--before_shmem_exit_index;
	else
		elog(ERROR, "before_shmem_exit callback (%p,0x%llx) is not the latest entry",
			 fc_function, (long long) fc_arg);
}

/* ----------------------------------------------------------------
 *		on_exit_reset
 *
 *		此函数清除所有注册的 on_proc_exit() 和 on_shmem_exit() 函数。
 *		在分叉一个后端之后使用，以便后端在退出时不会认为
 *		应该调用 postmaster 的退出程序...
 * ----------------------------------------------------------------
 */
void on_exit_reset(void)
{
	before_shmem_exit_index = 0;
	on_shmem_exit_index = 0;
	on_proc_exit_index = 0;
	reset_on_dsm_detach();
}

/* ----------------------------------------------------------------
 *		check_on_shmem_exit_lists_are_empty
 *
 *		调试检查，确保当前进程没有提前注册共享内存清理处理程序。
 * ----------------------------------------------------------------
 */
void check_on_shmem_exit_lists_are_empty(void)
{
	if (before_shmem_exit_index)
		elog(FATAL, "before_shmem_exit has been called prematurely");
	if (on_shmem_exit_index)
		elog(FATAL, "on_shmem_exit has been called prematurely");
	/* 检查 DSM 分离状态似乎是多余的，鉴于上述内容 */
}
